# coding=utf-8
import json

from django.contrib.auth.models import UserManager as DjangoUserManager
from django.db import models
from django.db import router
from django.db.models.fields.related import ForeignObject
from django.db.models.options import Options
from django.db.models.query import QuerySet
from django.db.models.signals import post_delete, pre_delete
from django.db.models.sql.datastructures import Join
from django.db.models.sql.where import ExtraWhere
from django.utils import timezone as tz
from rest_framework.exceptions import ValidationError

from social.utils.hashid import decode_model_ids
from social.utils.hashid import encode_model_ids


class BaseQuerySet(QuerySet):
    def _transform_hid(self, kwargs):
        hid = {}
        hids = {}
        for k, v in kwargs.items():
            if k in ['id', 'pk']:
                hid[k] = kwargs[k]
            elif k.startswith("id__in") or k.startswith("pk__in"):
                hids[k] = kwargs[k]
            # elif k.endswith("__id") or k.endswith("__pk"):
            #     hid[k] = kwargs[k]
            elif k.startswith("id__") or k.startswith("pk__"):
                hid[k] = kwargs[k]
        if not (hid or hids):
            return kwargs
        for k, v in hid.items():
            if str(v).isdigit():
                kwargs[k] = v
            else:
                kwargs[k] = decode_model_ids(v, None, ignore_error=True) or v

        for k, v in hids.items():
            if not v:
                continue
            kwargs[k] = [_ if str(_).isdigit() else decode_model_ids(_, self.model._meta.label, ignore_error=True) or _
                         for _ in v]
        return kwargs

    def filter(self, *args, **kwargs):
        kwargs = self._transform_hid(kwargs)
        return super(BaseQuerySet, self).filter(*args, **kwargs)


class BaseManager(models.Manager):
    def get_queryset(self):
        return BaseQuerySet(model=self.model, using=self._db, hints=self._hints)


class UserManager(DjangoUserManager, BaseManager):
    pass


class BaseModel(models.Model):
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    objects = BaseManager()

    @property
    def hid(self):
        hid = getattr(self, "__hid", None)
        if hid:
            return hid
        else:
            self.__hid = encode_model_ids(self._meta.label, self.pk)
        return self.__hid

    class Meta:
        abstract = True


class JSONField(models.Field):
    """
    A field that will ensure the data entered into it is valid JSON.
    """
    default_error_messages = {
        'invalid': "'%s' is not a valid JSON string."
    }
    description = "JSON object"

    def __init__(self, *args, **kwargs):
        if not kwargs.get('null', False):
            kwargs['default'] = kwargs.get('default', dict)
        self.encoder_kwargs = {
            'indent': kwargs.pop('indent', False),
        }

        self.decoder_kwargs = dict(kwargs.pop('decoder_kwargs', {}))
        super(JSONField, self).__init__(*args, **kwargs)
        self.validate(self.get_default(), None)

    def validate(self, value, model_instance):
        if not self.null and value is None:
            raise ValidationError(self.error_messages['null'])
        try:
            self.get_prep_value(value)
        except ValueError:
            raise ValidationError(self.error_messages['invalid'] % value)

    def get_default(self):
        if self.has_default():
            default = self.default
            if callable(default):
                default = default()
            return json.loads(json.dumps(default, **self.encoder_kwargs), **self.decoder_kwargs)
        return super(JSONField, self).get_default()

    def get_internal_type(self):
        return 'TextField'

    def db_type(self, connection):
        if connection.vendor == 'postgresql':
            # Only do jsonb if in pg 9.4+
            if connection.pg_version >= 90400:
                return 'jsonb'
            return 'text'
        if connection.vendor == 'mysql':
            return 'longtext'
        if connection.vendor == 'oracle':
            return 'long'
        return 'text'

    def from_db_value(self, value, expression, connection):
        if not value:
            return None
        return json.loads(value, **self.decoder_kwargs)

    # def get_db_prep_value(self, value, connection=None, prepared=None):
    #     return self.get_prep_value(value)

    def get_prep_value(self, value):
        if value is None:
            if not self.null and self.blank:
                return ""
            return None
        return json.dumps(value, default=str, **self.encoder_kwargs)

    def select_format(self, compiler, sql, params):
        if compiler.connection.vendor == 'postgresql' and self.decoder_kwargs.get('cls') is not None:
            # Avoid psycopg2's automatic decoding to allow custom decoder
            return '%s::text' % sql, params
        return super(JSONField, self).select_format(compiler, sql, params)

    def value_to_string(self, obj):
        return self.value_from_object(obj)
